package fr.aendawyn.as3lecteuraudio {
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.TimerEvent;
	import flash.media.ID3Info;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.media.SoundTransform;
	import flash.net.URLRequest;
	import flash.utils.Timer;
	import fr.aendawyn.as3lecteuraudio.erreurs.ErreurLecteurAudio;
	import fr.aendawyn.as3lecteuraudio.events.EventLecteurAudio;
	import fr.aendawyn.as3lecteuraudio.events.specialise.EventDebutLecture;
	import fr.aendawyn.as3lecteuraudio.events.specialise.EventFinLecture;
	import fr.aendawyn.as3lecteuraudio.events.specialise.EventInfosPiste;
	import fr.aendawyn.as3lecteuraudio.events.specialise.EventProgression;
	
	[Event(name = "debut_lecture",
		type = "fr.aendawyn.as3lecteuraudio.events.specialise.EventDebutLecture")]
	[Event(name = "fin_lecture",
		type = "fr.aendawyn.as3lecteuraudio.events.specialise.EventFinLecture")]
	[Event(name = "progression",
		type = "fr.aendawyn.as3lecteuraudio.events.specialise.EventProgression")]
	[Event(name = "infos_disponibles",
		type = "fr.aendawyn.as3lecteuraudio.events.specialise.EventInfosPiste")]
	/**	<p>La classe <code>LecteurAudio</code> permet de créer facilement un lecteur audio en AS3.</p>
	 * 	<p>Le lecteur audio gere les liste de lecture, la lecture aleatoire et repetee, la reprise
	 * 	de lecture, le changement de volume, ect...</p>
	 *
	 * @author Adrien Quillet
	 */
	public class LecteurAudio extends EventDispatcher {
		private var _ordreDeLectureAleatoire:Vector.<String>;
		private var _listeDeLecture:Vector.<String>;
		private var _lecturesPrecedentes:Vector.<String>;
		private var _pisteActuelle:PisteAudio;
		private var _son:Sound;
		private var _canalAudio:SoundChannel;
		private var _proprietesSon:SoundTransform;
		private var _positionLectureEnCours:uint;
		private var _numeroPisteActuelle:int;
		private var _lectureRepetee:Boolean;
		private var _lectureAleatoire:Boolean;
		private var _lectureEnCours:Boolean;
		private var _lectureEnPause:Boolean;
		private var _timerProgressionPiste:Timer;
		
		/**	Cree une instance de <code>LecteurAudio</code>.
		 *
		 */
		public function LecteurAudio() {
			trace("---------------\nConstruction d'un nouveau player");
			_listeDeLecture = new Vector.<String>;
			_ordreDeLectureAleatoire = new Vector.<String>;
			_lecturesPrecedentes = new Vector.<String>;
			_proprietesSon = new SoundTransform;
			_proprietesSon.volume = 1;
			trace("Volume : " + (_proprietesSon.volume * 100));
			_proprietesSon.pan = 0;
			trace("Balance : " + (_proprietesSon.pan * 100));
			_lectureAleatoire = false;
			trace("Lecture aleatoire : " + _lectureAleatoire);
			_lectureRepetee = true;
			trace("Lecture repetee : " + _lectureRepetee);
			_lectureEnCours = false;
			_lectureEnPause = false;
			_numeroPisteActuelle = -1;
			_timerProgressionPiste = new Timer(100);
			trace("La progression des pistes sera dispatchee toutes les " + _timerProgressionPiste.delay / 1000 + " s");
			_timerProgressionPiste.addEventListener(TimerEvent.TIMER, handlerDispatchProgression);
		}
		
		/*	----------------------------------------------------------- */
		/*	---------------		Gestion des pistes		--------------- */
		/*	----------------------------------------------------------- */
		/**	<p>Permet d'ajouter une piste à la liste de lecture courante.</p>
		 *
		 * 	@param urlPiste		l'url vers la piste à ajouter. Il peut être absolu ou relatif.
		 */
		public function ajouterPiste(urlPiste:String):void {
			trace("---------------\nAjout d'une piste");
			_listeDeLecture.push(urlPiste);
			trace("Piste ajoutee : " + urlPiste);
			ajoutePisteOrdreLectureAleatoire(urlPiste);
			dispatchEvent(new Event("event_changement_valeur_listeDeLecture_interne"));
		}
		
		/**	<p>Permet le retrait d'une piste à la liste de lecture courante.</p>
		 *
		 * 	@param identitePiste
		 *	<p>Peut être de deux types différents :
		 * 	<ul>
		 * 		<li><b>int</b> : la piste du même numéro est alors retirée
		 * 			de la liste de lecture courante</li>
		 * 		<li><b>String</b> : la piste possedant l'url spécifié est alors
		 * 			retirée de la liste de lecture courante</li>
		 * 		</ul>
		 * 	</p>
		 * 	@throws ErreurLecteurAudio si le type de <code>identitepiste</code> est autre que
		 * 	<code>int</code> ou <code>String</code>.
		 */
		public function retirerPiste(identitePiste:*):void {
			trace("---------------\nRetrait d'une piste");
			if (identitePiste is String) {
				trace("Retrait d'une piste par son URL");
				trace("Piste a retirer : " + identitePiste);
				retirerPisteURL(identitePiste);
			} else if (identitePiste is int || identitePiste is Number || identitePiste is uint) {
				trace("Retrait d'une piste par son numero");
				trace("Numero de la piste :" + identitePiste);
				retirerPisteNum(identitePiste);
			} else {
				trace("Erreur de retrait");
				throw new ErreurLecteurAudio("Impossible de retirer la piste " + String(identitePiste) + ".");
			}
			dispatchEvent(new Event("event_changement_valeur_listeDeLecture_interne"));
		}
		
		private function retirerPisteURL(urlPiste:String):void {
			var nouvelleListeDeLecture:Vector.<String> = new Vector.<String>;
			var url:String;
			var urlPisteEscape:String = escape(urlPiste);
			for each (url in _listeDeLecture) {
				if (urlPisteEscape.length - urlPisteEscape.search(escape(url)) != escape(url).length)
					nouvelleListeDeLecture.push(url);
			}
			_listeDeLecture = nouvelleListeDeLecture;
			var nouvelOrdreDeLectureAleatoire:Vector.<String> = new Vector.<String>;
			for each (url in _ordreDeLectureAleatoire) {
				if (urlPisteEscape.length - urlPisteEscape.search(escape(url)) != escape(url).length)
					nouvelOrdreDeLectureAleatoire.push(url);
			}
			_ordreDeLectureAleatoire = nouvelOrdreDeLectureAleatoire;
		}
		
		private function retirerPisteNum(num:int):void {
			var tailleListeLecture:int = _listeDeLecture.length;
			var nouvelleListeDeLecture:Vector.<String> = new Vector.<String>;
			var i:int;
			for (i = 0; i < tailleListeLecture; i++)
				if (num != i)
					nouvelleListeDeLecture.push(_listeDeLecture[i]);
			_listeDeLecture = nouvelleListeDeLecture;
			var nouvelOrdreDeLectureAleatoire:Vector.<String> = new Vector.<String>;
			for (i = 0; i < tailleListeLecture; i++)
				if (num != i)
					nouvelOrdreDeLectureAleatoire.push(_ordreDeLectureAleatoire[i]);
			_ordreDeLectureAleatoire = nouvelOrdreDeLectureAleatoire;
		}
		
		private function ajoutePisteOrdreLectureAleatoire(urlPiste:String):void {
			var nombreAlea:int = Math.floor(Math.random() * _listeDeLecture.length);
			_ordreDeLectureAleatoire.push(urlPiste);
			var tmp:String = _ordreDeLectureAleatoire[nombreAlea];
			_ordreDeLectureAleatoire[nombreAlea] =
				_ordreDeLectureAleatoire[_ordreDeLectureAleatoire.length - 1];
			_ordreDeLectureAleatoire[_ordreDeLectureAleatoire.length - 1] = tmp;
		}
		
		private function genereOrdreLectureAleatoire():void {
			_ordreDeLectureAleatoire = new Vector.<int>;
			while (_ordreDeLectureAleatoire.length != _listeDeLecture.length) {
				var nombreAlea:int;
				do {
					nombreAlea = (Math.random() * _listeDeLecture.length + 1);
				} while (_ordreDeLectureAleatoire.indexOf(nombreAlea) != -1);
				_ordreDeLectureAleatoire.push(nombreAlea);
			}
		}
		
		/*	----------------------------------------------------------- */
		/*	---------------		Contrôle de lecture		--------------- */
		/*	----------------------------------------------------------- */
		/**	<p>Lance la lecture de la liste de lecture courante.</p>
		 *
		 * 	@param numeroPiste
		 * 	Si différent de -1, lance la lecture de la piste correspondant au numéro spécifié
		 * 	en paramètre.
		 * 	@throws ErreurLecteurAudio si <code>numeroPiste</code> est spécifié et invalide (plus petit
		 * 	ou égal à 0, plus grand que le nombre de pistes dans la liste de lecture)
		 */
		public function lecture(numeroPiste:int = -1):void {
			trace("---------------\nLecture");
			if (numeroPiste != -1) {
				trace("Une demande de piste specifique a ete faite");
				if (numeroPiste < 1 || numeroPiste > _listeDeLecture.length) {
					trace("Numero de piste incoherent");
					trace("Volume valide : [1;" + _listeDeLecture.length + "]");
					throw new ErreurLecteurAudio("Le numero de piste demande est invalide.");
				}
				trace("La piste numero " + numeroPiste + " a ete demandee");
				var urlPisteDemandee:String = _listeDeLecture[numeroPiste - 1];
				trace("Fichier demande : " + urlPisteDemandee);
				if (_lectureEnCours)
					finaliserLecturePiste();
				lecturePiste(urlPisteDemandee);
			} else {
				if (_lectureEnCours && _lectureEnPause) {
					trace("Reprise d'une lecture en pause");
					_lectureEnPause = false;
					_canalAudio = _son.play(_positionLectureEnCours, 0, _proprietesSon);
					trace("Piste : " + _son.url);
					_canalAudio.addEventListener(Event.SOUND_COMPLETE, handlerLectureTerminee);
				} else if (!_lectureEnCours) {
					trace("Lecture d'une nouvelle piste");
					if (_listeDeLecture.length != 0) {
						var urlPiste:String;
						var ancienNum:int = _numeroPisteActuelle;
						do {
							_numeroPisteActuelle =
								(_numeroPisteActuelle + 1) % _listeDeLecture.length;
						} while (_numeroPisteActuelle == _listeDeLecture.length - 2 &&
							ancienNum == _listeDeLecture.length - 2);
						if (_numeroPisteActuelle < 0)
							_numeroPisteActuelle += _listeDeLecture.length;
						if (_lectureAleatoire) {
							urlPiste = _ordreDeLectureAleatoire[_numeroPisteActuelle];
						} else {
							urlPiste = _listeDeLecture[_numeroPisteActuelle];
						}
						trace("Piste demandee : " + urlPiste);
						lecturePiste(urlPiste);
					}
				}
			}
		}
		
		private function lecturePiste(url:String):void {
			_son = new Sound;
			_son.addEventListener(IOErrorEvent.IO_ERROR, handlerIOError);
			_son.addEventListener(Event.ID3, handlerID3);
			_son.addEventListener(Event.COMPLETE, handlerChargementTermine);
			_pisteActuelle = new PisteAudio(nomFichier(url));
			dispatchEvent(new Event("event_changement_valeur_pisteActuelle_interne"));
			_son.load(new URLRequest(url));
		}
		
		private function finaliserLecturePiste():void {
			try {
				trace("Fermeture du fichier et du canal audio");
				_timerProgressionPiste.stop();
				_canalAudio.stop();
				_canalAudio = null;
				/*	Si le fichier audio était sur le disque local, _son.close()
				provoque l'erreur 2029. On catch les erreurs provenant de
				la fermeture du flex */
				_son.close();
			} catch (e:Error) {
				trace("Erreur car le fichier est local");
				trace(e);
				trace("La fermeture est quand meme effectuee");
			}
			_son = null;
		}
		
		/**	<p>Met en pause la lecture courante. Un appel à <code>lecture()</code> permet la
		 * 	reprise.</p>
		 *
		 */
		public function pause():void {
			if (_lectureEnCours) {
				trace("---------------\nLecure en pause");
				_lectureEnPause = true;
				_positionLectureEnCours = _canalAudio.position;
				trace("Position de la piste actuelle : " + _positionLectureEnCours);
				_canalAudio.stop();
			}
		}
		
		/**	<p>Arrête la lecture courante. Un appel à <code>lecture()</code> relance la piste
		 * 	qui a été arrêtée.</p>
		 *
		 */
		public function stop():void {
			if (_lectureEnCours) {
				trace("---------------\nLecure stoppee");
				finaliserLecturePiste();
				_lectureEnCours = false;
				_positionLectureEnCours = 0;
				_numeroPisteActuelle--;
				dispatchEvent(new EventProgression(0, 0));
			}
		}
		
		/**	<p>Stoppe la lecture courante et passe à la prochaine piste dans la liste de lecture.</p>
		 * 	<p>Si la fin de la liste de lecture est atteinte et que <code>lectureRepetee</code> est
		 * 	à <code>true</code>, la lecture reprend au début de la liste de lecture.</p>
		 * 	<p>S'il n'y avait pas de lecture en cours, la lecture se lance.</p>
		 *
		 */
		public function suivant():void {
			trace("---------------\nLecure suivante");
			_lectureEnCours = false;
			_lectureEnPause = false;
			finaliserLecturePiste();
			lecture();
		}
		
		/**	<p>Stoppe la lecture courante et passe à la prochaine pistepiste précédente dans la
		 * 	liste de lecture.</p>
		 * 	<p>Si la fin de la liste de lecture est atteinte et que <code>lectureRepetee</code> est
		 * 	à <code>true</code>, la lecture reprend au début de la liste de lecture.</p>
		 * 	<p>S'il n'y avait pas de lecture en cours, la lecture se lance.</p>
		 */
		public function precedent():void {
			trace("---------------\nLecure precedente");
			_lectureEnCours = false;
			_lectureEnPause = false;
			finaliserLecturePiste();
			var urlPiste:String;
			_numeroPisteActuelle = (_numeroPisteActuelle - 1) % _listeDeLecture.length;
			if (_numeroPisteActuelle < 0)
				_numeroPisteActuelle += _listeDeLecture.length;
			if (_lectureAleatoire) {
				urlPiste = _ordreDeLectureAleatoire[_numeroPisteActuelle];
			} else {
				urlPiste = _listeDeLecture[_numeroPisteActuelle];
			}
			lecturePiste(urlPiste);
		}
		
		/*	----------------------------------------------------------- */
		/*	---------------			Handlers			--------------- */
		/*	----------------------------------------------------------- */
		/**	<p>Se déclenche quand un fichier son est complètement chargé en mémoire.</p>
		 *
		 * 	@param e
		 * 	type Event.COMPLETE
		 */
		protected function handlerChargementTermine(e:Event):void {
			trace("---------------\nChargement fichier termine");
			trace("Fichier charge :" + _son.url);
			_son.removeEventListener(Event.COMPLETE, handlerChargementTermine);
			_lectureEnCours = true;
			_canalAudio = _son.play(0, 0, _proprietesSon);
			dispatchEvent(new EventDebutLecture(unescape(_son.url),
														 _listeDeLecture.indexOf(unescape(_son.url)) + 1));
			trace("Lecture demarree");
			_canalAudio.addEventListener(Event.SOUND_COMPLETE, handlerLectureTerminee);
			_timerProgressionPiste.start();
			dispatchEvent(new EventInfosPiste(_pisteActuelle));
		}
		
		/**	<p>Se déclenche lors d'une erreur d'entrée/sortie.</p>
		 *
		 * 	@param e
		 * 	type IOErrorEvent.IO_ERROR
		 * 	@throws ErreurLecteurAudio retransmet l'erreur captée
		 */
		protected function handlerIOError(e:IOErrorEvent):void {
			trace("---------------\nErreur I/O");
			trace(e);
			throw new ErreurLecteurAudio(e.text);
		}
		
		/**	<p>Se déclenche à la reception des informations ID3 d'une piste audio.</p>
		 *
		 * 	@param e
		 * 	type Event.ID3
		 */
		protected function handlerID3(e:Event):void {
			trace("---------------\nInfos ID3 recues");
			_son.removeEventListener(Event.ID3, handlerID3);
			var titre:String;
			if (_son.id3.songName == "" || _son.id3.songName == null) {
				trace("Pas d'infos ID3 sur le titre");
				trace("Le nom du fichier sera le titre de la piste actuelle");
				titre = pisteActuelle.titre
			} else {
				titre = _son.id3.songName;
			}
			_pisteActuelle =
				new PisteAudio(titre, _son.id3.artist, int(_son.id3.track), _son.id3.album,
							   int(_son.id3.year), _son.id3.genre, _son.id3.comment);
			trace("Resume ID3 Titre - Auteur : " + _pisteActuelle.titre + ' - ' + _pisteActuelle.artiste);
			dispatchEvent(new Event("event_changement_valeur_pisteActuelle_interne"));
			dispatchEvent(new EventInfosPiste(_pisteActuelle));
		}
		
		/**	<p>Se déclenche quand la lecture d'une piste audio est terminée.</p>
		 *
		 * 	@param e
		 * 	type Event.SOUND_COMPLETE
		 */
		protected function handlerLectureTerminee(e:Event):void {
			trace("---------------\nLecture terminee");
			_son.removeEventListener(IOErrorEvent.IO_ERROR, handlerIOError);
			_canalAudio.removeEventListener(Event.SOUND_COMPLETE, handlerLectureTerminee);
			dispatchEvent(new EventFinLecture(unescape(_son.url)));
			finaliserLecturePiste();
			_lectureEnCours = false;
			_lectureEnPause = false;
			if (_numeroPisteActuelle != _listeDeLecture.length - 1 || _lectureRepetee) {
				trace("Lecture suivante demandee");
				lecture();
			}
		}
		
		/**	<p>Permet de dispatcher la progression de la lecture courante</p>
		 *
		 * 	@param e
		 * 	type TimerEvent.TIMER
		 */
		protected function handlerDispatchProgression(e:TimerEvent):void {
			dispatchEvent(new EventProgression(_canalAudio.position, _son.length));
		}
		
		/*	----------------------------------------------------------- */
		/*	---------------			Getters				--------------- */
		/*	----------------------------------------------------------- */
		[Bindable(event = "event_changement_valeur_volume_interne")]
		/**	<p>Volume général du lecteur audio.</p>
		 * 	<p>Le volume est compris entre <code>0</code> (muet) et <code>100</code>
		 *  (maximum).</p>
		 *
		 * @return un <code>int</code> compris entre <code>0</code> et <code>100</code>
		 */
		public function get volume():int {
			return Math.round(_proprietesSon.volume * 100);
		}
		
		[Bindable(event = "event_changement_valeur_balance_interne")]
		/**	<p>Balance générale du lecteur audio. La balance permet d'équilibrer
		 * 	les voies droite/gauche du signal audio.</p>
		 *	<p>La balance est comprise entre <code>-100</code> (tout à gauche) et <code>100</code>
		 * 	(tout à droite). La valeur <code>0</code> correspond à l'équilibre.</p>
		 * @return	un <code>int</code> commpris entre <code>-100</code> et <code>100</code>
		 */
		public function get balance():int {
			return Math.round(_proprietesSon.pan * 100);
		}
		
		[Bindable(event = "event_changement_valeur_lectureAleatoire_interne")]
		/**	<p>Définit si les pistes de la liste de lecture sont jouées aléatoirement ou non.</p>
		 * 	<p>Lors d'une lecture aléatoire, les pistes de la liste de lecture sont jouées dans le
		 * 	désordre, et aucune piste n'est répétée tant que toutes les pistes n'ont pas été lues.</p>
		 *
		 * @return	<code>true</code> si la lecture est aléatoire, <code>false</code> sinon
		 */
		public function get lectureAleatoire():Boolean {
			return _lectureAleatoire;
		}
		
		[Bindable(event = "event_changement_valeur_lectureRepetee_interne")]
		/**	<p>Définit si les pistes de la liste de lecture sont jouées en boucle ou non.</p>
		 *
		 * @return	<code>true</code> si la lecture est repetee, <code>false</code> sinon
		 */
		public function get lectureRepetee():Boolean {
			return _lectureRepetee;
		}
		
		[Bindable(event = "event_changement_valeur_position_interne")]
		/**	<p>Definit la position de la tete de lecture sur la piste courante. La position est exprimee en milliseconde 
		 * 	(1 s = 1000 ms / 1ms = 0,001s)</p>
		 *
		 * @return	un entier representant le temps ecoule depuis le debut de la piste en millisecondes
		 */
		public function get positionLecture():uint {
			if (_canalAudio)
				return _canalAudio.position;
			else
				return 0;
		}
		
		[Bindable(event = "event_changement_valeur_pisteActuelle_interne")]
		/**	<p>Piste qui est actuellement a l'ecoute. Permet d'acceder aux informations de la piste, telles que
		 * 	le titre, l'auteur ou encore l'album</p>
		 *
		 * 	@return	une instance de <code>PisteAudio</code>
		 */
		public function get pisteActuelle():PisteAudio {
			return _pisteActuelle;
		}
		
		[Bindable(event = "event_changement_valeur_listeDeLecture_interne")]
		/**	<p>Tableau contenant le nom des fichiers appartenant à la liste de lecture courante.</p>
		 *
		 *	@return 	un Array de String
		 */
		public function get listeDeLecture():Array {
			var liste:Array = new Array;
			for each (var url:String in _listeDeLecture)
				liste.push(nomFichier(url));
			return liste;
		}
		
		/*	----------------------------------------------------------- */
		/*	---------------			Setters				--------------- */
		/*	----------------------------------------------------------- */
		/**
		 *
		 * @param value
		 * @throws ErreurLecteurAudio
		 */
		public function set volume(value:int):void {
			trace("---------------\nAffectation volume");
			if (value < 0 || value > 100) {
				trace("Nouveau volume incoherent");
				trace("Volume voulu : " + value);
				trace("Volume valide : [0;100]");
				throw new ErreurLecteurAudio("Valeur incohérente pour le volume.");
			}
			_proprietesSon.volume = value / 100;
			if (_canalAudio)
				_canalAudio.soundTransform = _proprietesSon;
			trace("Nouveau volume : " + value);
			dispatchEvent(new Event("event_changement_valeur_volume_interne"));
		}
		
		/**
		 *
		 * @param value
		 * @throws ErreurLecteurAudio
		 */
		public function set balance(value:int):void {
			trace("---------------\nAffectation balance");
			if (value < -100 || value > 100) {
				trace("Nouvelle balance incoherente");
				trace("Balance voulue : " + value);
				trace("Balance valide : [-100;100]");
				throw new ErreurLecteurAudio("Valeur incohérente pour la balance.");
			}
			_proprietesSon.pan = value / 100;
			if (_canalAudio)
				_canalAudio.soundTransform = _proprietesSon;
			trace("Nouvelle balance : " + value);
			dispatchEvent(new Event("event_changement_valeur_balance_interne"));
		}
		
		/**
		 *
		 * @param value
		 */
		public function set lectureAleatoire(value:Boolean):void {
			trace("---------------\nAffectation lecture aleatoire");
			_lectureAleatoire = value;
			trace("Lecture aleatoire : " + _lectureAleatoire);
			dispatchEvent(new Event("event_changement_valeur_lectureAleatoire_interne"));
		}
		
		/**
		 *
		 * @param value
		 */
		public function set lectureRepetee(value:Boolean):void {
			trace("---------------\nAffectation lecture repetee");
			_lectureRepetee = value;
			trace("Lecture repetee : " + _lectureRepetee);
			dispatchEvent(new Event("event_changement_valeur_lectureRepetee_interne"));
		}
		
		/**
		 *
		 * @param value
		 * @throws ErreurLecteurAudio
		 */
		public function set positionLecture(value:uint):void {
			trace("---------------\nModification position tete de lecture");
			if (value < 0 || value > _son.length) {
				trace("Nouvelle position incoherente");
				trace("Position voulue : " + value);
				trace("Intervalle valide : [0;" + _son.length + "]");
				throw new ErreurLecteurAudio("La nouvelle position de lecvture est invalide.");
			}
			trace("Nouvelle position : " + value);
			trace("Appel d'une pause et d'une lecture a suivre");
			pause();
			_positionLectureEnCours = value;
			lecture();
			dispatchEvent(new Event("event_changement_valeur_position_interne"));
		}
		
		/*	----------------------------------------------------------- */
		/*	---------------			Utils				--------------- */
		/*	----------------------------------------------------------- */
		private function nomFichier(url:String):String {
			var split:Array = url.split('/');
			return unescape(split[split.length - 1]);
		}
	}
}
